package org.simpleFramework.core.inject;


import lombok.extern.slf4j.Slf4j;
import org.simpleFramework.core.BeanContainer;
import org.simpleFramework.core.inject.annotation.Autowired;
import org.simpleFramework.core.utils.ClassUtil;
import org.simpleFramework.core.utils.ValidationUtil;

import java.lang.reflect.Field;
import java.util.Set;

/**
 * @Author: sijunqiang
 * @Date: 2020/4/19 12:32
 * @Description: 该类是提供依赖的注入的逻辑
 */
@Slf4j
public class DependencyInject {


    /**
     * 通过无参构造获取Bean容器的单例实例
     */
    private BeanContainer beanContainer;

    public DependencyInject() {
        beanContainer = BeanContainer.getInstance();
    }


    /**
     * 执行ioc
     * 该方法的功能是：对整个容器的Class对象 去做依赖注入的处理
     */
    public void doIoc() {

        Set<Class<?>> classSet = beanContainer.getClasses();
        if (ValidationUtil.isEmpty(classSet)) {
            log.warn("empty classSet in BeanContainer");
            return;
        }

        //1：遍历Bean容器中的所有的Class对象
        for (Class<?> clazz : classSet) {

            //2： 遍历Class对象的所有的成员变量
            Field[] fields = clazz.getDeclaredFields();
            if (ValidationUtil.isEmpty(fields)) {
                //continue 标识跳出当前循环。 return 标识停止
                continue;
            }
            for (Field field : fields) {
                //3：找出被Autowired标记的成员变量
                if (field.isAnnotationPresent(Autowired.class)) {

                    // 获取注解里面的属性值
                    Autowired autowired = field.getAnnotation(Autowired.class);
                    String autowiredValue = autowired.value();

                    //4：获取成员变量的类型
                    Class<?> fieldClass = field.getType();
                    //5：获取这些成员变量的类型在容器里对应的实例
                    Object fieldValue = getFieldInstance(fieldClass, autowiredValue);
                    if (fieldValue == null) {
                        throw new RuntimeException("unable to inject relevant type, target filedClass is:  " + fieldClass.getTypeName());
                    } else {
                        //6：通过反射将对应的成员变量实例注入到成员变量所在类的实例里面
                        // 根据Class 获取Class对应的实例(就是对象)
                        Object targetBean = beanContainer.getBean(clazz);
                        ClassUtil.setFieldValue(field, targetBean, fieldValue, true);
                    }
                }
            }


        }


    }

    /**
     * <P>根据Class在beanContainer容器里 获取类的实例或者是接口的是实现类</P>
     *
     * @param fieldClass 属性的Class
     * @return 实例
     */
    private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
        // 1:根据属性的类型 获取其对应的实例
        Object fieldValue = beanContainer.getBean(fieldClass);
        // 若存在实例 返回
        if (fieldValue != null) {
            return fieldValue;
        }
        //不存在 1：接口 2：真不存在
        else {
            Class<?> implementedClass = getImplementClass(fieldClass, autowiredValue);
            if (implementedClass != null) {
                return beanContainer.getBean(implementedClass);
            } else {
                return null;
            }
        }
    }


    /**
     * <P>获取接口的是实现类的Class对象</P>
     *
     * @param fieldClass     属性的Class
     * @param autowiredValue 这个参数的是@AutoWired注解上面的默认值 用来标记具体实现那个类
     * @return
     */

    private Class<?> getImplementClass(Class<?> fieldClass, String autowiredValue) {
        //1:获取子类的class集合
        Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);
        //如果字类集合不为空的话
        if (!ValidationUtil.isEmpty(classSet)) {
            // 2:如果用户没有指定具体的那个实现类
            if (ValidationUtil.isEmpty(autowiredValue)) {
                if (classSet.size() == 1) {
                    return classSet.iterator().next();
                } else {
                    throw new RuntimeException("multiple implemented classes for " + fieldClass.getName() + "please set @AutoWired value to pick one");
                }
            } else {
                for (Class<?> clazz : classSet) {
                    if (autowiredValue.equals(clazz.getSimpleName())) {
                        return clazz;
                    }
                }
            }
        }
        return null;
    }
}
